#include <common.h>
#include <asm.h>
#include <asm/unistd.h>
#include <os/loader.h>
#include <os/irq.h>
#include <os/sched.h>
#include <os/lock.h>
#include <os/kernel.h>
#include <os/task.h>
#include <os/string.h>
#include <os/mm.h>
#include <os/time.h>
#include <sys/syscall.h>
#include <screen.h>
#include <printk.h>
#include <assert.h>
#include <type.h>
#include <csr.h>

extern void ret_from_exception();

// Task info array
task_info_t tasks[TASK_MAXNUM];

// [p1-task5] batch info
struct batch_info_t{
    char batch_order[20];
    int length;
};
struct batch_info_t batch_info;


static void init_jmptab(void)
{
    volatile long (*(*jmptab))() = (volatile long (*(*))())KERNEL_JMPTAB_BASE;

    jmptab[CONSOLE_PUTSTR]  = (long (*)())port_write;
    jmptab[CONSOLE_PUTCHAR] = (long (*)())port_write_ch;
    jmptab[CONSOLE_GETCHAR] = (long (*)())port_read_ch;
    jmptab[SD_READ]         = (long (*)())sd_read;
    jmptab[QEMU_LOGGING]    = (long (*)())qemu_logging;
    jmptab[SET_TIMER]       = (long (*)())set_timer;
    jmptab[READ_FDT]        = (long (*)())read_fdt;
    jmptab[MOVE_CURSOR]     = (long (*)())screen_move_cursor;
    jmptab[PRINT]           = (long (*)())printk;
    jmptab[YIELD]           = (long (*)())do_scheduler;
    jmptab[MUTEX_INIT]      = (long (*)())do_mutex_lock_init;
    jmptab[MUTEX_ACQ]       = (long (*)())do_mutex_lock_acquire;
    jmptab[MUTEX_RELEASE]   = (long (*)())do_mutex_lock_release;
}

static int init_task_info()
{
    // TODO: [p1-task4] Init 'tasks' array via reading app-info sector
    // NOTE: You need to get some related arguments from bootblock first
    int* taskinfo_ptr = (int*)(0x52000000);
    int taskinfo = *taskinfo_ptr;
    int tasknum = taskinfo & 0x0000ffff;
    unsigned int taskinfo_loc_offset = (taskinfo & 0xffff0000) >> 16;

    task_info_t* pointer = (task_info_t*)(taskinfo_loc_offset + 0x52010000);
    memcpy(tasks, pointer, tasknum * sizeof(task_info_t));

    return tasknum;
    
}


static void init_pcb_stack(
    ptr_t kernel_stack, ptr_t user_stack, ptr_t entry_point,
    pcb_t *pcb)
{
      /* TODO: [p2-task3] initialization of registers on kernel stack
      * HINT: sp, ra, sepc, sstatus
      * NOTE: To run the task in user mode, you should set corresponding bits
      *     of sstatus(SPP, SPIE, etc.).
      */
    regs_context_t *pt_regs =
        (regs_context_t *)(kernel_stack - sizeof(regs_context_t));
    
    *(reg_t*)((ptr_t)pt_regs + 8)   = (reg_t) entry_point; //set the value of ra
    *(reg_t*)((ptr_t)pt_regs + 16)  = (reg_t) user_stack;  //set the value of sp
    *(reg_t*)((ptr_t)pt_regs + 256) = (reg_t) SR_SPIE; //set the sstatus's SPIE 1, and set the sstatus's SPP 0;
    *(reg_t*)((ptr_t)pt_regs + 264) = (reg_t) entry_point; //set the value of sepc
    
    
    
     /* TODO: [p2-task1] set sp to simulate just returning from switch_to
      * NOTE: you should prepare a stack, and push some values to
      * simulate a callee-saved context.
      */
    switchto_context_t *pt_switchto =
        (switchto_context_t *)((ptr_t)pt_regs - sizeof(switchto_context_t));
    
    //initialize the pcb->kernel_sp and pcb->user_sp
    pcb->kernel_sp = (reg_t)pt_switchto;
    pcb->user_sp   = (reg_t)user_stack;
    
    //initialize the value of reg ra and reg sp
    *(reg_t*)((ptr_t)pt_switchto)     = (reg_t)ret_from_exception;
    *(reg_t*)((ptr_t)pt_switchto + 8) = (reg_t)pt_switchto;


}

static void init_pcb(void)
{
    /* TODO: [p2-task1] load needed tasks and init their corresponding PCB */
    int taskid[8] = {2,5,4,6,1,3,8,7};
    int numpage = 2;
    ptr_t entrypoint, ret1, ret2;
    for(int i = 0;i < 8;i++){
        ret1 = allocKernelPage(numpage);
        ret2 = allocUserPage(numpage);
        entrypoint = load_task_img(taskid[i]);
        init_pcb_stack(ret1 + numpage*PAGE_SIZE, ret2 + numpage*PAGE_SIZE, entrypoint, (pcb_t*)(pcb+i));
        EN_LIST(ready_queue,pcb[i].list);
        pcb[i].pid = i+1;
        pcb[i].status = TASK_READY;
        pcb[i].cursor_x = 0;
        pcb[i].cursor_y = 0;
    }


    /* TODO: [p2-task1] remember to initialize 'current_running' */
    current_running = &pid0_pcb;

}

static void init_syscall(void)
{
    // TODO: [p2-task3] initialize system call table.
   syscall[SYSCALL_SLEEP]         = (long (*)())do_sleep;
   syscall[SYSCALL_YIELD]         = (long (*)())do_scheduler;
   syscall[SYSCALL_WRITE]         = (long (*)())screen_write;
   syscall[SYSCALL_CURSOR]        = (long (*)())screen_move_cursor;
   syscall[SYSCALL_REFLUSH]       = (long (*)())screen_reflush;
   syscall[SYSCALL_GET_TIMEBASE]  = (long (*)())get_time_base;
   syscall[SYSCALL_GET_TICK]      = (long (*)())get_ticks;
   syscall[SYSCALL_LOCK_INIT]     = (long (*)())do_mutex_lock_init;
   syscall[SYSCALL_LOCK_ACQ]      = (long (*)())do_mutex_lock_acquire;
   syscall[SYSCALL_LOCK_RELEASE]  = (long (*)())do_mutex_lock_release;
   //task5
   syscall[SYSCALL_THREAD_CREATE] = (long (*)())thread_create;
   
   
}

int main(void)
{
    // Init jump table provided by kernel and bios(ΦωΦ)
    init_jmptab();

    // Init task information (〃'▽'〃)
    int tasknum = init_task_info();


    // Init Process Control Blocks |•'-'•) ✧
    init_pcb();
    printk("> [INIT] PCB initialization succeeded.\n");

    // Read CPU frequency (｡•ᴗ-)_
    time_base = bios_read_fdt(TIMEBASE);

    // Init lock mechanism o(´^｀)o
    init_locks();
    printk("> [INIT] Lock mechanism initialization succeeded.\n");

    // Init interrupt (^_^)
    init_exception();
    printk("> [INIT] Interrupt processing initialization succeeded.\n");

    // Init system call table (0_0)
    init_syscall();
    printk("> [INIT] System call initialized successfully.\n");

    // Init screen (QAQ)
    init_screen();
    printk("> [INIT] SCREEN initialization succeeded.\n");

    // TODO: [p2-task4] Setup timer interrupt and enable all interrupt globally
    // NOTE: The function of sstatus.sie is different from sie's
    //enable_interrupt();
    reg_t tp_value = (reg_t)&pid0_pcb;
    asm volatile(
    "mv tp, %0"
    :
    :"r"(tp_value)
    );
    
    bios_set_timer(get_ticks() + TIMER_INTERVAL);
    

    // Infinite while loop, where CPU stays in a low-power state (QAQQQQQQQQQQQ)
    while (1)
    {
       // If you do non-preemptive scheduling, it's used to surrender control
        //do_scheduler();

        // If you do preemptive scheduling, they're used to enable CSR_SIE and wfi
        enable_preempt();
        asm volatile("wfi");
    }

    return 0;
}
